package com.example.lt.service.user.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.lt.constant.AuditStatus;
import com.example.lt.constant.CommonConstants;
import com.example.lt.constant.RedisConstant;
import com.example.lt.context.BaseContext;
import com.example.lt.entity.dto.FindPWDTO;
import com.example.lt.entity.dto.RegisterDTO;
import com.example.lt.entity.dto.UpdateUserDTO;
import com.example.lt.entity.queryPage.BasePage;
import com.example.lt.entity.response.AuditResponse;
import com.example.lt.entity.user.*;
import com.example.lt.entity.video.Type;
import com.example.lt.entity.vo.UserVO;
import com.example.lt.exception.BaseException;
import com.example.lt.mapper.user.UserMapper;
import com.example.lt.service.FileService;
import com.example.lt.service.InterestPushService;
import com.example.lt.service.audit.ImageAuditService;
import com.example.lt.service.audit.TextAuditService;
import com.example.lt.service.user.FavoritesService;
import com.example.lt.service.user.FollowService;
import com.example.lt.service.user.UserService;
import com.example.lt.service.user.UserSubscribeService;
import com.example.lt.service.video.TypeService;
import com.example.lt.util.RedisUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Author 小涛Tao
 * @Date: 2024/07/27/17:04
 * version 1.0 注释：用户服务层实现类
 **/
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    private final FavoritesService favoritesService;

    private final FollowService followService;

    private final UserSubscribeService userSubscribeService;

    private final TypeService typeService;

    private final RedisUtil redisUtil;

    private final InterestPushService interestPushService;

    private final TextAuditService textAuditService;

    private final ImageAuditService imageAuditService;

    private final FileService fileService;

    /**
     * 注册用户信息，并创建用户默认收藏夹
     * @param registerDTO
     * @return
     */
    @Override
    public Boolean register(RegisterDTO registerDTO) {
        final User user = User.builder()
                .nickName(registerDTO.getNickName())
                .email(registerDTO.getEmail())
                .password(registerDTO.getPassword())
                .description(CommonConstants.DEFAULT_DESCRIPTION)
                .build();

        // 保存新用户信息
        if (!save(user)) return false;

        // 创建默认收藏夹
        final Favorites favorites = Favorites.builder()
                .userId(user.getId())
                .name(CommonConstants.DEFAULT_FAVORITES)
                .build();

        favoritesService.save(favorites); // 创建用户默认收藏夹

        // 设置用户默认收藏夹id
        user.setDefaultFavoritesId(favorites.getId());
        updateById(user);

        return true;
    }

    /**
     * 找回密码
     * @param findPWDTO
     * @return
     */
    @Override
    public Boolean findPassword(FindPWDTO findPWDTO) {
        final User user = User.builder()
                .email(findPWDTO.getEmail())
                .password(findPWDTO.getNewPassword())
                .build();

        boolean update = update(user, new UpdateWrapper<User>().lambda()
                .set(User::getPassword, findPWDTO.getNewPassword())
                .eq(User::getEmail, findPWDTO.getEmail()));

        return update;
    }

    /**
     * 根据邮箱 email 查找用户，判断邮箱是否已存在
     * @param email
     * @return false：该邮箱还未注册  true：该邮箱已被注册
     */
    @Override
    public Boolean getUserByEmail(String email) {
        final User user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getEmail, email));
        if (user == null) {
            return false;
        }
        return true;
    }

    /**
     * 修改用户信息，需要检查 名称、个人描述、头像是否需要审核，还有默认文件夹的修改需要检查文件夹是否存在
     * @param userDTO 用户信息封装类
     */
    @Override
    public void updateUser(UpdateUserDTO userDTO) {
        final Long userId = BaseContext.get();

        final User oldUser = getById(userId);

        if (!oldUser.getNickName().equals(userDTO.getNickName())) {
            // 如果用户有修改名称，则需要对名称进行审核
            final AuditResponse audit = textAuditService.audit(userDTO.getNickName());
            if (audit.getAuditStatus() != AuditStatus.SUCCESS) {
                // 如果审核不通过，则抛出异常让全局异常处理器处理，并返回违规信息
                throw new BaseException(audit.getMsg());
            }
            oldUser.setNickName(userDTO.getNickName()); // 将旧的名称换成新的
        }

        if (!ObjectUtils.isEmpty(userDTO.getDescription()) && !oldUser.getDescription().equals(userDTO.getDescription())) {
            // 如果用户有个人描述信息，并且描述信息不是原来的，则也需要对个人描述进行审核
            final AuditResponse audit = textAuditService.audit(userDTO.getDescription());
            if (audit.getAuditStatus() != AuditStatus.SUCCESS) {
                // 如果审核不通过，则抛出异常让全局异常处理器处理，并返回违规信息
                throw new BaseException(audit.getMsg());
            }
            oldUser.setDescription(userDTO.getDescription()); // 将旧的个人描述换成新的描述
        }

        if (!Objects.equals(userDTO.getAvatar(), oldUser.getAvatar())) {
            // 如果头像有更换，则也需要进行审核
            final AuditResponse audit = imageAuditService.audit(fileService.getById(userDTO.getAvatar()).getFileKey());
            if (audit.getAuditStatus() != AuditStatus.SUCCESS) {
                // 如果审核不通过，则抛出异常让全局异常处理器处理，并返回违规信息
                throw new BaseException(audit.getMsg());
            }
            oldUser.setAvatar(userDTO.getAvatar()); // 把旧头像换成新头像
        }

        if (!ObjectUtils.isEmpty(userDTO.getDefaultFavoritesId())) {
            // 如果是更改默认收藏夹，则对收藏夹id进行校验
            favoritesService.exist(userId, userDTO.getDefaultFavoritesId());

            oldUser.setDefaultFavoritesId(userDTO.getDefaultFavoritesId()); // 修改默认文件夹
        }

        oldUser.setSex(userDTO.getSex());
        updateById(oldUser);
    }

    /**
     * 获取用户信息，包括关注和粉丝列表
     * @param userId
     * @return
     */
    @Override
    public UserVO getInfo(Long userId) {
        // 根据用户id查询用户
        final User user = getById(userId);
        if (ObjectUtils.isEmpty(user)) {
            return new UserVO();
        }

        final UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO); // 将user中的属性复制到userVO中

        long followCount = followService.getFollowCount(userId); // 获取关注数量
        long fansCount = followService.getFansCount(userId); // 获取粉丝数量

        userVO.setFollow(followCount);
        userVO.setFans(fansCount);

        return userVO;
    }

    /**
     * 获取关注人列表，包括关注人的基本信息
     * @param basePage
     * @param userId
     * @return
     */
    @Override
    public Page<User> getFollows(BasePage basePage, Long userId) {
        Page<User> page = new Page<>();
        // 获取关注列表，是一个id集合
        final Collection<Long> followIds = followService.getFollow(userId, basePage);
        // 如果关注列表为空，则直接返回 page
        if (ObjectUtils.isEmpty(followIds)) return page;
        // 获取粉丝列表
        final HashSet<Long> fans = new HashSet<>();
        // 这里需要将数据转换，因为存到redis中数值小是用int保存，取出来需要用long比较
        fans.addAll(followService.getFans(userId, null));

        // 遍历关注列表，然后将每一个的关注人与自己的互关情况记录起来
        Map<Long,Boolean> map = new HashMap<>();
        for (Long followId : followIds) {
            map.put(followId, fans.contains(followId));
        }

        // 获取关注的基本信息
        final List<User> users = new ArrayList<>();
        final Map<Long, User> userMap = getBaseInfoUserToMap(map.keySet());
        for (Long followId : followIds) {
            final User user = userMap.get(followId);
            user.setEach(map.get(user.getId())); // 设置关注人与自己的互关情况
            users.add(user);
        }
        // 设置分页 page 的结果和总数并返回
        page.setRecords(users);
        page.setTotal(users.size());
        return page;
    }

    /**
     * 获取粉丝列表，包括粉丝的基本信息
     * @param basePage
     * @param userId
     * @return
     */
    @Override
    public Page<User> getFans(BasePage basePage, Long userId) {
        Page<User> page = new Page<>();
        // 获取粉丝列表
        final Collection<Long> fansIds = followService.getFans(userId, basePage);
        if (ObjectUtils.isEmpty(fansIds)) return page;
        // 获取关注列表
        final HashSet<Long> followIds = new HashSet<>();
        followIds.addAll(followService.getFollow(userId, null));

        // 遍历粉丝列表，然后将每一个的粉丝与自己的互关情况记录起来
        Map<Long,Boolean> map = new HashMap<>();
        for (Long fansId : fansIds) {
            map.put(fansId,followIds.contains(fansId));
        }

        // 获取粉丝的基本信息
        final Map<Long, User> userMap = getBaseInfoUserToMap(map.keySet());
        final List<User> users = new ArrayList<>();
        for (Long fansId : fansIds) {
            final User user = userMap.get(fansId);
            user.setEach(map.get(user.getId())); // 设置粉丝与自己的互关情况
            users.add(user);
        }
        // 设置分页 page 的结果和总数并返回
        page.setRecords(users);
        page.setTotal(users.size());
        return page;
    }

    /**
     * 订阅或取消订阅分类，并更新 redis 中的模型
     * @param typeIds 被订阅的分类id集合
     */
    @Override
    public void subscribe(HashSet<Long> typeIds) {
        final Long userId = BaseContext.get();
        // 获取之前被订阅的分类的id
        final Set<Long> oldTypeIds = userSubscribeService.list(new LambdaQueryWrapper<UserSubscribe>().eq(UserSubscribe::getUserId, userId))
                .stream().map(UserSubscribe::getTypeId).collect(Collectors.toSet());

        Collection<Type> oldTypes; // 之前被订阅分类
        List<String> oldLabels = new ArrayList<>(); // 之前被订阅的分类的所有标签
        // 如果之前有订阅的分类，则获取到这些分类的所有标签
        if (!ObjectUtils.isEmpty(oldTypeIds)) {
            oldTypes = typeService.listByIds(oldTypeIds);
            for (Type oldType : oldTypes) {
                oldLabels.addAll(oldType.buildLabel());
            }
        }

        // 如果传进来的 typeIds 为空，那就是当前用户没有订阅任何分类，直接删除 UserSubscribe 表中当前用户的所有记录
        // 同时，删除 redis 中的旧标签
        if (ObjectUtils.isEmpty(typeIds)) {
            userSubscribeService.remove(new LambdaQueryWrapper<UserSubscribe>().eq(UserSubscribe::getUserId, userId));
            redisUtil.hdel(RedisConstant.USER_MODEL + userId, oldLabels);
            return;
        }

        // 查询出现在订阅的分类
        final Collection<Type> types = typeService.listByIds(typeIds);
        if (types.size() != typeIds.size()) {
            throw new BaseException("不存在的分类");
        }

        final ArrayList<UserSubscribe> userSubscribes = new ArrayList<>();
        for (Long typeId : typeIds) {
            final UserSubscribe userSubscribe = new UserSubscribe();
            userSubscribe.setUserId(userId);
            userSubscribe.setTypeId(typeId);
            userSubscribes.add(userSubscribe);
        }
        // 删除所有旧的记录
        userSubscribeService.remove(new LambdaQueryWrapper<UserSubscribe>().eq(UserSubscribe::getUserId, userId));
        // 添加新的记录
        userSubscribeService.saveBatch(userSubscribes);

        // 初始化用户模型
        final UserModelVO userModelVO = new UserModelVO();
        userModelVO.setUserId(userId);
        // 获取分类下的标签
        List<String> labels = new ArrayList<>();
        for (Type type : types) {
            labels.addAll(type.buildLabel());
        }
        userModelVO.setLabels(labels);
        // 调用初始化模型方法
        initModel(userModelVO, oldLabels);
    }

    /**
     * 获取用户 userId 订阅的分类
     * @param userId
     * @return
     */
    @Override
    public Collection<Type> listSubscribeType(Long userId) {
        if (userId == null) return Collections.EMPTY_LIST;
        // 获取用户订阅了的所有分类的id
        final List<Long> typeIds = userSubscribeService.list(new LambdaQueryWrapper<UserSubscribe>().eq(UserSubscribe::getUserId, userId))
                .stream().map(UserSubscribe::getTypeId).collect(Collectors.toList());
        // 如果id集合为空则说明用户没有订阅任何分类，直接返回空集合
        if (ObjectUtils.isEmpty(typeIds)) return Collections.EMPTY_LIST;
        // 查询分类的基本信息
        final List<Type> types = typeService.list(new LambdaQueryWrapper<Type>()
                .in(Type::getId, typeIds)
                .select(Type::getId, Type::getName, Type::getIcon));
        // 返回分类集合
        return types;
    }

    /**
     * 获取用户 userId 没有订阅的分类
     * @param userId
     * @return
     */
    @Override
    public Collection<Type> listNoSubscribeType(Long userId) {
        // 获取用户订阅的分类
        final Set<Long> typeIdsSet = userSubscribeService.list(new LambdaQueryWrapper<UserSubscribe>().eq(UserSubscribe::getUserId, userId))
                .stream().map(UserSubscribe::getTypeId).collect(Collectors.toSet());
        // 获取所有的分类
        final List<Type> allType = typeService.list(null);
        // 没有订阅的分类的集合
        final ArrayList<Type> types = new ArrayList<>();
        // 如果分类的id不在用户订阅的分类id集合中，则这个分类就是用户没有订阅的分类
        for (Type type : allType) {
            if (!typeIdsSet.contains(type.getId())) {
                types.add(type);
            }
        }
        return types; // 返回集合
    }

    /**
     * 批量获取用户基本信息
     * @param userIds 用户ID集合
     * @return
     */
    @Override
    public List<User> list(Collection<Long> userIds) {
        return list(new LambdaQueryWrapper<User>().in(User::getId, userIds)
                .select(User::getId, User::getNickName, User::getSex, User::getAvatar, User::getDescription));
    }

    /**
     * 关注 或 取关
     * @param followsUserId
     */
    @Override
    public boolean follows(Long followsUserId) {
        final Long userId = BaseContext.get();

        return followService.follows(userId, followsUserId);
    }

    /**
     * 修改用户模型
     * @param userModel
     */
    @Override
    public void updateUserModel(UserModel userModel) {
        interestPushService.updateUserModel(userModel);
    }

    /**
     * 添加用户搜索记录
     * @param userId 用户id
     * @param search 搜索关键字
     */
    @Override
    public void addSearchHistory(Long userId, String search) {
        if (!ObjectUtils.isEmpty(userId)) {
            redisUtil.zadd(RedisConstant.USER_SEARCH_HISTORY + userId, new Date().getTime(), search, RedisConstant.USER_SEARCH_HISTORY_TIME);
        }
    }

    /**
     * 获取用户搜索记录
     * @param userId 用户id
     * @return
     */
    @Override
    public Collection<String> searchHistory(Long userId) {
        List<String> searchList = new ArrayList<>();
        if (!ObjectUtils.isEmpty(userId)) {
            searchList.addAll(redisUtil.zGet(RedisConstant.USER_SEARCH_HISTORY + userId));
            searchList = searchList.subList(0, searchList.size() > 20 ? 20 : searchList.size());
        }
        return searchList;
    }

    // 初始化模型
    public void initModel(UserModelVO userModelVO, List<String> oldLabels) {
        interestPushService.initUserModel(userModelVO.getUserId(), userModelVO.getLabels(), oldLabels);
    }

    /**
     * 批量获取用户信息，并以 Map 形式返回，key 为用户的id value 为用户本身（user）
     * @param userIds 用户id集合
     * @return
     */
    public Map<Long, User> getBaseInfoUserToMap(Collection<Long> userIds) {
        List<User> users = new ArrayList<>();
        // 根据 userIds 集合来批量查询用户信息，制定了五个字段
        if (!ObjectUtils.isEmpty(userIds)) {
            users = list(new LambdaQueryWrapper<User>()
                    .in(User::getId, userIds)
                    .select(User::getId, User::getNickName, User::getDescription, User::getSex, User::getAvatar));
        }
        // 转换成 Map ，key 为用户的id value 为用户本身（user）
        return users.stream().collect(Collectors.toMap(User::getId, Function.identity()));
    }

}
